home *** CD-ROM | disk | FTP | other *** search
Wrap
{ Piping data from and to processes and process signaling Copyright (C) 1998-2001 Free Software Foundation, Inc. Author: Frank Heckenbach <frank@pascal.gnu.de> This file is part of GNU Pascal. GNU Pascal is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Pascal is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Pascal; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. As a special exception, if you link this file with files compiled with a GNU compiler to produce an executable, this does not cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. } {$gnu-pascal,B-,I-} {$if __GPC_RELEASE__ < 20000412} {$error This unit requires GPC release 20000412 or newer.} {$endif} { Keep this consistent with the one in pipec.c } {$if defined (MSDOS) or defined (__MINGW32__)} {$define NOFORK} {$endif} unit Pipe; interface uses GPC; (*$local W-*)type PPStrings = ^TPStrings;TPStrings (Count: Cardinal) = array [1 .. Count] of ^String; (*@@two unit bug with definition in gpc.pas in programs using these two units*) (*$endlocal*) const PipeForking = {$ifdef NOFORK} False {$else} True {$endif}; type TProcedure = procedure; PWaitPIDResult = ^TWaitPIDResult; TWaitPIDResult = (PIDNothing, PIDExited, PIDSignaled, PIDStopped, PIDUnknown); PPipeProcess = ^TPipeProcess; TPipeProcess = record PID : Integer; { Process ID of process forked } SignalPID : Integer; { Process ID to send the signal to. Equals PID by default } OpenPipes : Integer; { Number of pipes to/from the process, for internal use } Signal : Integer; { Send this signal (if not 0) to the process after all pipes have been closed after some time } Seconds : Integer; { Wait so many seconds before sending the signal if the process has not terminated by itself } Wait : Boolean; { Wait for the process, even longer than Seconds seconds, after sending the signal (if any) } Result : PWaitPIDResult; { Default nil. If a pointer to a variable is stored here, its destination will contain the information whether the process terminated by itself, or was terminated or stopped by a signal, when waiting after closing the pipes } Status : ^Integer; { Default nil. If a pointer to a variable is stored here, its destination will contain the exit status if the process terminated by itself, or the number of the signal otherwise, when waiting after closing the pipes } end; const EFork = 600; { cannot fork `%s'' } ESpawn = 601; { cannot spawn `%s'' } var { Default values for TPipeProcess records created by Pipe } DefaultPipeSignal : Integer = 0; DefaultPipeSeconds : Integer = 0; DefaultPipeWait : Boolean = True; { The procedure Pipe starts a process whose name is given by ProcessName, with the given parameters (can be null if no parameters) and environment, and create pipes from and/or to the process' standard input/output/error. ProcessName is searched for in the PATH with FSearchExecutable. Any of ToInputFile, FromOutputFile and FromStdErrFile can be null if the corresponding pipe is not wanted. FromOutputFile and FromStdErrFile may be identical, in which case standard output and standard error are redirected to the same pipe. The behaviour of other pairs of files being identical is undefined, and useless, anyway. The files are Assigned and Reset or Rewritten as appropriate. Errors are returned in IOResult. If Process is not null, a pointer to a record is stored there, from which the PID of the process created can be read, and by writing to which the action after all pipes have been closed can be changed. (The record is automatically Dispose'd of after all pipes have been closed.) If automatic waiting is turned off, the caller should get the PID from the record before it's Dispose'd of, and wait for the process sometime in order to avoid zombies. If no redirections are performed (i.e., all 3 files are null), the caller should wait for the process with WaitPipeProcess. When an error occurs, Process is not assigned to, and the state of the files is undefined, so be sure to check IOResult before going on. ChildProc, if not nil, is called in the child process after forking and redirecting I/O, but before executing the new process. It can even be called instead of executing a new process (ProcessName can be empty then). The procedure even works under Dos, but, of course, in a limited sense: if ToInputFile is used, the process will not actually be started until ToInputFile is closed. Signal, Seconds and Wait of TPipeProcess are ignored, and PID and SignalPID do not contain a Process ID, but an internal value without any meaning to the caller. Result will always be PIDExited. So, Status is the only interesting field (but Result should also be checked). Since there is no forking under Dos, ChildProc, if not nil, is called in the main process before spawning the program. So, to be portable, it should not do any things that would influence the process after the return of the Pipe function. The only portable way to use "pipes" in both directions is to call `Pipe', write all the Input data to ToInputFile, close ToInputFile, and then read the Output and StdErr data from FromOutputFile and FromStdErrFile. However, since the capacity of pipes is limited, one should also check for Data from FromOutputFile and FromStdErrFile (using CanRead, IOSelect or IOSelectRead) while writing the Input data (under Dos, there simply won't be any data then, but checking for data doesn't do any harm). Please see pipedemo.pas for an example. } (*@@IO critical*) procedure Pipe (var ToInputFile, FromOutputFile, FromStdErrFile : AnyFile; (*@@fjf265 const*) ProcessName : String; protected var Parameters : TPStrings; ProcessEnvironment : PCStrings; var Process : PPipeProcess; ChildProc : TProcedure); { Waits for a process created by Pipe as determined in the Process record. (Process is Dispose'd of afterwards.) Returns True if successful. } function WaitPipeProcess (Process : PPipeProcess) : Boolean; implementation {$L pipec.c} {$ifndef NOFORK} type PInteger = ^Integer; PFileProcess = ^TFileProcess; TFileProcess = record FilePtr : PAnyFile; OldCloseProc : TCloseProc; ProcessPtr : PPipeProcess end; function CPipe (Path : CString; CProcessParameters, ProcessEnvironment : PCStrings; PPipeStdIn, PPipeStdOut, PPipeStdErr : PInteger; ChildProc : TProcedure) : Integer; asmname '_p_pipe'; function WaitPipeProcess (Process : PPipeProcess) : Boolean; var Dummy : Boolean; function DoWaitPID (Block : Boolean) = WPID : Integer; var Result : TWaitPIDResult; WStatus, Status : Integer; begin WPID := WaitPID (Process^.PID, WStatus, Block); if WPID <= 0 then begin Result := PIDNothing; Status := 0 end else if StatusExited (WStatus) then begin Result := PIDExited; Status := StatusExitCode (WStatus) end else if StatusSignaled (WStatus) then begin Result := PIDSignaled; Status := StatusTermSignal (WStatus) end else if StatusStopped (WStatus) then begin Result := PIDStopped; Status := StatusStopSignal (WStatus) end else begin Result := PIDUnknown; Status := 0 end; if Process^.Result <> nil then Process^.Result^ := Result; if Process^.Status <> nil then Process^.Status^ := Status end; begin WaitPipeProcess := True; with Process^ do begin while Seconds <> 0 do begin if DoWaitPID (False) = PID then begin Dispose (Process); Exit end; Sleep (1); Dec (Seconds) end; if Signal <> 0 then Dummy := Kill (SignalPID, Signal); if not Wait or (DoWaitPID (True) <= 0) then WaitPipeProcess := False else Dispose (Process) end end; procedure PipeTFDDClose (var PrivateData); var FileProcess : TFileProcess absolute PrivateData; OpenProc : TOpenProc; SelectFunc : TSelectFunc; SelectProc : TSelectProc; ReadFunc : TReadFunc; WriteFunc : TWriteFunc; FlushProc : TFlushProc; DoneProc : TDoneProc; FPrivateData : Pointer; Dummy : Boolean; begin with FileProcess do begin GetTFDD (FilePtr^, OpenProc, SelectFunc, SelectProc, ReadFunc, WriteFunc, FlushProc, null, DoneProc, FPrivateData); SetTFDD (FilePtr^, OpenProc, SelectFunc, SelectProc, ReadFunc, WriteFunc, FlushProc, OldCloseProc, DoneProc, FPrivateData); Close (FilePtr^); Dec (ProcessPtr^.OpenPipes); if ProcessPtr^.OpenPipes = 0 then Dummy := WaitPipeProcess (ProcessPtr) end; Dispose (@FileProcess) end; procedure SetCloseAction (var f : AnyFile; PProcess : PPipeProcess); var p : PFileProcess; OpenProc : TOpenProc; SelectFunc : TSelectFunc; SelectProc : TSelectProc; ReadFunc : TReadFunc; WriteFunc : TWriteFunc; FlushProc : TFlushProc; DoneProc : TDoneProc; PrivateData : Pointer; begin if InOutRes <> 0 then Exit; New (p); p^.FilePtr := @f; p^.ProcessPtr := PProcess; Inc (PProcess^.OpenPipes); GetTFDD (f, OpenProc, SelectFunc, SelectProc, ReadFunc, WriteFunc, FlushProc, p^.OldCloseProc, DoneProc, PrivateData); SetTFDD (f, OpenProc, SelectFunc, SelectProc, ReadFunc, WriteFunc, FlushProc, PipeTFDDClose, DoneProc, p) end; procedure Pipe (var ToInputFile, FromOutputFile, FromStdErrFile : AnyFile; (*@@fjf265 const*) ProcessName : String; protected var Parameters : TPStrings; ProcessEnvironment : PCStrings; var Process : PPipeProcess; ChildProc : TProcedure); var ParameterCount, i : Cardinal; PipeStdIn, PipeStdOut, PipeStdErr : Integer; PPipeStdIn, PPipeStdOut, PPipeStdErr : PInteger; PID : Integer; PProcess : PPipeProcess; ProcessPath : TString; begin if @Process <> nil then Process := nil; if InOutRes <> 0 then Exit; if @Parameters = nil then ParameterCount := 0 else ParameterCount := Parameters.Count; var CProcessParameters : array [0 .. ParameterCount + 1] of CString; CProcessParameters [0] := ProcessName; for i := 1 to ParameterCount do CProcessParameters [i] := Parameters [i]^; CProcessParameters [ParameterCount + 1] := nil; if @ToInputFile = nil then PPipeStdIn := nil else PPipeStdIn := @PipeStdIn; if @FromOutputFile = nil then PPipeStdOut := nil else PPipeStdOut := @PipeStdOut; if @FromStdErrFile = nil then PPipeStdErr := nil else if @FromStdErrFile = @FromOutputFile then PPipeStdErr := @PipeStdOut else PPipeStdErr := @PipeStdErr; if ProcessName = '' then ProcessPath := '' else ProcessPath := FSearchExecutable (ProcessName, GetEnv (PathEnvVar)); if (ProcessName <> '') and (ProcessPath = '') then PID := - 1 else PID := CPipe (ProcessPath, PCStrings (@CProcessParameters), ProcessEnvironment, PPipeStdIn, PPipeStdOut, PPipeStdErr, ChildProc); if PID <= 0 then begin IOErrorCString (EFork, ProcessName); Exit end; New (PProcess); PProcess^.PID := PID; PProcess^.SignalPID := PID; PProcess^.Signal := DefaultPipeSignal; PProcess^.Seconds := DefaultPipeSeconds; PProcess^.Wait := DefaultPipeWait; PProcess^.OpenPipes := 0; PProcess^.Result := nil; PProcess^.Status := nil; if @Process <> nil then Process := PProcess; if @ToInputFile <> nil then begin AssignHandle (ToInputFile, PipeStdIn); Rewrite (ToInputFile); SetCloseAction (ToInputFile, PProcess) end; if @FromOutputFile <> nil then begin AssignHandle (FromOutputFile, PipeStdOut); Reset (FromOutputFile); SetCloseAction (FromOutputFile, PProcess) end; if (@FromStdErrFile <> nil) and (@FromStdErrFile <> @FromOutputFile) then begin AssignHandle (FromStdErrFile, PipeStdErr); Reset (FromStdErrFile); SetCloseAction (FromStdErrFile, PProcess) end end; {$else} { NOTE: This emulation code is quite a mess! Be warned if you want to understand it, and don't make any quick changes here unless you fully understand it. } function CPipe (Path : CString; CProcessParameters, ProcessEnvironment : PCStrings; NameStdIn, NameStdOut, NameStdErr : CString; ChildProc : TProcedure) : Integer; asmname '_p_pipe'; type PPipeData = ^TPipeData; TPipeData = record ProcName, Path : CString; ParameterCount : Cardinal; CProcessParameters, CProcessEnvironment : PCStrings; NameStdOut, NameStdErr, NameStdIn : TString; CNameStdOut, CNameStdErr, CNameStdIn : CString; PToInputFile, PFromOutputFile, PFromStdErrFile : ^AnyFile; InternalToInputFile : Text; aChildProc : TProcedure; PipeProcess : TPipeProcess end; PFileProcess = ^TFileProcess; TFileProcess = record FilePtr : PAnyFile; OldCloseProc : TCloseProc; PipeDataPtr : PPipeData end; { TPipeProcess.PID actually holds the exit status here } function WaitPipeProcess (Process : PPipeProcess) : Boolean; begin if Process^.Result <> nil then Process^.Result^ := PIDExited; if Process^.Status <> nil then Process^.Status^ := Process^.PID; WaitPipeProcess := Process^.PID >= 0 end; procedure DoPipe (var PipeData : TPipeData); var i : Cardinal; Dummy : Boolean; begin with PipeData do begin PipeProcess.PID := CPipe (Path, CProcessParameters, CProcessEnvironment, CNameStdIn, CNameStdOut, CNameStdErr, aChildProc); PipeProcess.SignalPID := PipeProcess.PID; if PToInputFile <> nil then Erase (InternalToInputFile); if PipeProcess.PID < 0 then IOErrorCString (ESpawn, ProcName); Dispose (ProcName); Dispose (Path); for i := 0 to ParameterCount do Dispose (CProcessParameters^[i]); Dispose (CProcessParameters); Dummy := WaitPipeProcess (@PipeProcess); if PipeProcess.PID < 0 then Exit; if PFromOutputFile <> nil then begin Reset (PFromOutputFile^, NameStdOut); Erase (PFromOutputFile^) end; if (PFromStdErrFile <> nil) and (PFromStdErrFile <> PFromOutputFile) then begin Reset (PFromStdErrFile^, NameStdErr); Erase (PFromStdErrFile^) end end end; function PipeInTFDDWrite (var PrivateData; const Buffer; Size : SizeType) : SizeType; var Data : TPipeData absolute PrivateData; CharBuffer : array [1 .. Size] of Char absolute Buffer; begin with Data do Write (InternalToInputFile, CharBuffer); PipeInTFDDWrite := Size end; procedure PipeInTFDDClose (var PrivateData); var Data : TPipeData absolute PrivateData; begin with Data do begin Close (InternalToInputFile); if PFromOutputFile <> nil then Close (PFromOutputFile^); if (PFromStdErrFile <> nil) and (PFromStdErrFile <> PFromOutputFile) then Close (PFromStdErrFile^); Dec (Data.PipeProcess.OpenPipes); DoPipe (Data); Dispose (@Data) end end; procedure PipeOutTFDDClose (var PrivateData); var FileProcess : TFileProcess absolute PrivateData; OpenProc : TOpenProc; SelectFunc : TSelectFunc; SelectProc : TSelectProc; ReadFunc : TReadFunc; WriteFunc : TWriteFunc; FlushProc : TFlushProc; DoneProc : TDoneProc; FPrivateData : Pointer; Dummy : Boolean; begin with FileProcess do begin GetTFDD (FilePtr^, OpenProc, SelectFunc, SelectProc, ReadFunc, WriteFunc, FlushProc, null, DoneProc, FPrivateData); SetTFDD (FilePtr^, OpenProc, SelectFunc, SelectProc, ReadFunc, WriteFunc, FlushProc, OldCloseProc, DoneProc, FPrivateData); Close (FilePtr^); Dec (PipeDataPtr^.PipeProcess.OpenPipes); if PipeDataPtr^.PipeProcess.OpenPipes = 0 then begin Dummy := WaitPipeProcess (@PipeDataPtr^.PipeProcess); Dispose (PipeDataPtr) end end; Dispose (@FileProcess) end; procedure SetCloseAction (var f : AnyFile; PipeData : PPipeData); var p : PFileProcess; OpenProc : TOpenProc; SelectFunc : TSelectFunc; SelectProc : TSelectProc; ReadFunc : TReadFunc; WriteFunc : TWriteFunc; FlushProc : TFlushProc; DoneProc : TDoneProc; PrivateData : Pointer; begin if InOutRes <> 0 then Exit; New (p); p^.FilePtr := @f; p^.PipeDataPtr := PipeData; GetTFDD (f, OpenProc, SelectFunc, SelectProc, ReadFunc, WriteFunc, FlushProc, p^.OldCloseProc, DoneProc, PrivateData); SetTFDD (f, OpenProc, SelectFunc, SelectProc, ReadFunc, WriteFunc, FlushProc, PipeOutTFDDClose, DoneProc, p) end; { The "pipes" that come from the process are reset to nothing until the process is started. Use a TFDD, not an empty file or the null device, so that IOSelect will not select these files. } procedure ResetNothing (var f : AnyFile); begin AssignTFDD (f, nil, nil, nil, nil, nil, nil, nil, nil, nil); Reset (f) end; procedure Pipe (var ToInputFile, FromOutputFile, FromStdErrFile : AnyFile; (*@@fjf265 const*) ProcessName : String; protected var Parameters : TPStrings; ProcessEnvironment : PCStrings; var Process : PPipeProcess; ChildProc : TProcedure); var i : Cardinal; PipeData : PPipeData; ProcessPath : TString; begin if @Process <> nil then Process := nil; if ProcessName = '' then ProcessPath := '' else ProcessPath := FSearchExecutable (ProcessName, GetEnv (PathEnvVar)); if (ProcessName <> '') and (ProcessPath = '') then begin IOErrorCString (ESpawn, ProcessName); Exit end; New (PipeData); with PipeData^ do begin aChildProc := ChildProc; ProcName := NewCString (ProcessName); Path := NewCString (ProcessPath); if @Parameters = nil then ParameterCount := 0 else ParameterCount := Parameters.Count; GetMem (CProcessParameters, (ParameterCount + 2) * SizeOf (CString)); CProcessParameters^[0] := NewCString (ProcessName); for i := 1 to ParameterCount do CProcessParameters^[i] := NewCString (Parameters [i]^); CProcessParameters^[ParameterCount + 1] := nil; CProcessEnvironment := ProcessEnvironment; PipeProcess.PID := - 1; PipeProcess.SignalPID := - 1; PipeProcess.Signal := DefaultPipeSignal; PipeProcess.Seconds := DefaultPipeSeconds; PipeProcess.Wait := DefaultPipeWait; PipeProcess.OpenPipes := 0; PipeProcess.Result := nil; PipeProcess.Status := nil; if @Process <> nil then Process := @PipeProcess; PToInputFile := @ToInputFile; PFromOutputFile := @FromOutputFile; PFromStdErrFile := @FromStdErrFile; if @FromOutputFile = nil then CNameStdOut := nil else begin Inc (PipeProcess.OpenPipes); NameStdOut := GetTempFileName; CNameStdOut := NameStdOut; if @ToInputFile <> nil then ResetNothing (FromOutputFile) end; if @FromStdErrFile = nil then CNameStdErr := nil else if @FromStdErrFile = @FromOutputFile then CNameStdErr := CNameStdOut else begin Inc (PipeProcess.OpenPipes); NameStdErr := GetTempFileName; CNameStdErr := NameStdErr; if @ToInputFile <> nil then ResetNothing (FromStdErrFile) end; if @ToInputFile = nil then begin CNameStdIn := nil; DoPipe (PipeData^); if @FromOutputFile <> nil then SetCloseAction (FromOutputFile, PipeData); if (@FromStdErrFile <> nil) and (@FromStdErrFile <> @FromOutputFile) then SetCloseAction (FromStdErrFile, PipeData) end else begin Inc (PipeProcess.OpenPipes); NameStdIn := GetTempFileName; CNameStdIn := NameStdIn; Rewrite (InternalToInputFile, NameStdIn); AssignTFDD (ToInputFile, nil, nil, nil, nil, PipeInTFDDWrite, nil, PipeInTFDDClose, nil, PipeData); Rewrite (ToInputFile) end end end; {$endif} end.